#ifndef __OCPP_CesiumCartographic_H__
#define __OCPP_CesiumCartographic_H__

#include "Cartesian3.h"
#include "CesiumMath.h"
#include "scaleToGeodeticSurface.h"

namespace OCPP
{
	namespace Cesium
	{
		class _CesiumExport Cartographic
		{
		public:
			double longitude, latitude, height;
			Cartographic(double _x = 0.0, double _y = 0.0, double _z = 0.0)
				: longitude(_x)
				, latitude(_y)
				, height(_z)
			{

			}

			Cartographic(const Cartographic& other)
			{
				this->longitude = other.longitude;
				this->latitude = other.latitude;
				this->height = other.height;
			}

			Cartographic& operator = (const Cartographic& other)
			{
				this->longitude = other.longitude;
				this->latitude = other.latitude;
				this->height = other.height;

				return (Cartographic&)*this;
			}

			/**
			 * Creates a new Cartographic instance from longitude and latitude
			 * specified in radians.
			 *
			 * @param {number} longitude The longitude, in radians.
			 * @param {number} latitude The latitude, in radians.
			 * @param {number} [height=0.0] The height, in meters, above the ellipsoid.
			 * @param {Cartographic} [result] The object onto which to store the result.
			 * @returns {Cartographic} The modified result parameter or a new Cartographic instance if one was not provided.
			 */
			static Cartographic fromRadians(double longitude, double latitude, double height = 0.0) {
				Cartographic result;
				result.longitude = longitude;
				result.latitude = latitude;
				result.height = height;
				return result;
			};

			/**
			 * Creates a new Cartographic instance from longitude and latitude
			 * specified in degrees.  The values in the resulting object will
			 * be in radians.
			 *
			 * @param {number} longitude The longitude, in degrees.
			 * @param {number} latitude The latitude, in degrees.
			 * @param {number} [height=0.0] The height, in meters, above the ellipsoid.
			 * @param {Cartographic} [result] The object onto which to store the result.
			 * @returns {Cartographic} The modified result parameter or a new Cartographic instance if one was not provided.
			 */
			static Cartographic fromDegrees(double longitude, double latitude, double height = 0.0) {
				longitude = CesiumMath::toRadians(longitude);
				latitude = CesiumMath::toRadians(latitude);

				return Cartographic::fromRadians(longitude, latitude, height);
			};

			const static Cartesian3 wgs84OneOverRadii;
			const static Cartesian3 wgs84OneOverRadiiSquared;
			inline const static double wgs84CenterToleranceSquared = CesiumMath::EPSILON1;

			/**
			 * Creates a new Cartographic instance from a Cartesian position. The values in the
			 * resulting object will be in radians.
			 *
			 * @param {Cartesian3} cartesian The Cartesian position to convert to cartographic representation.
			 * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the position lies.
			 * @param {Cartographic} [result] The object onto which to store the result.
			 * @returns {Cartographic} The modified result parameter, new Cartographic instance if none was provided, or undefined if the cartesian is at the center of the ellipsoid.
			 */
			static Cartographic fromCartesian(Cartesian3 cartesian) {
				auto oneOverRadii = wgs84OneOverRadii;
				auto oneOverRadiiSquared = wgs84OneOverRadiiSquared;
				auto centerToleranceSquared = wgs84CenterToleranceSquared;

				//`cartesian is required.` is thrown from scaleToGeodeticSurface
				auto p = scaleToGeodeticSurface1(
					cartesian,
					oneOverRadii,
					oneOverRadiiSquared,
					centerToleranceSquared
				);

				//return Cartographic();

				auto n = Cartesian3::multiplyComponents(
					p,
					oneOverRadiiSquared
				);
				n = Cartesian3::normalize(n);

				auto h = Cartesian3::subtract(cartesian, p);

				auto longitude = atan2(n.y, n.x);
				auto latitude = asin(n.z);
				auto height =
					CesiumMath::sign(Cartesian3::dot(h, cartesian)) * Cartesian3::magnitude(h);

				return Cartographic(longitude, latitude, height);
			};

			/**
			 * Creates a new Cartesian3 instance from a Cartographic input. The values in the inputted
			 * object should be in radians.
			 *
			 * @param {Cartographic} cartographic Input to be converted into a Cartesian3 output.
			 * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the position lies.
			 * @param {Cartesian3} [result] The object onto which to store the result.
			 * @returns {Cartesian3} The position
			 */
			static Cartesian3 toCartesian(Cartographic cartographic) {
				return Cartesian3::fromRadians(
					cartographic.longitude,
					cartographic.latitude,
					cartographic.height
				);
			};

			/**
			 * Compares the provided cartographics componentwise and returns
			 * <code>true</code> if they are equal, <code>false</code> otherwise.
			 *
			 * @param {Cartographic} [left] The first cartographic.
			 * @param {Cartographic} [right] The second cartographic.
			 * @returns {boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
			 */
			static bool equals(Cartographic left, Cartographic right) {
				return (
					left.longitude == right.longitude &&
					left.latitude == right.latitude &&
					left.height == right.height
					);
			}

			/**
			 * Compares the provided cartographics componentwise and returns
			 * <code>true</code> if they are within the provided epsilon,
			 * <code>false</code> otherwise.
			 *
			 * @param {Cartographic} [left] The first
			 * @param {Cartographic} [right] The second
			 * @param {number} [epsilon=0] The epsilon to use for equality testing.
			 * @returns {boolean} <code>true</code> if left and right are within the provided epsilon, <code>false</code> otherwise.
			 */
			static bool equalsEpsilon(Cartographic left, Cartographic right, double epsilon = 0.0) {
				return (
					std::abs(left.longitude - right.longitude) <= epsilon &&
					std::abs(left.latitude - right.latitude) <= epsilon &&
					std::abs(left.height - right.height) <= epsilon
					);
			};

			/**
			 * An immutable Cartographic instance initialized to (0.0, 0.0, 0.0).
			 *
			 * @type {Cartographic}
			 * @constant
			 */
			const static Cartographic ZERO;

			/**
			 * Compares the provided against this cartographic componentwise and returns
			 * <code>true</code> if they are equal, <code>false</code> otherwise.
			 *
			 * @param {Cartographic} [right] The second cartographic.
			 * @returns {boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
			 */
			bool equals(Cartographic right) {
				return Cartographic::equals(*this, right);
			};

			/**
			 * Compares the provided against this cartographic componentwise and returns
			 * <code>true</code> if they are within the provided epsilon,
			 * <code>false</code> otherwise.
			 *
			 * @param {Cartographic} [right] The second cartographic.
			 * @param {number} [epsilon=0] The epsilon to use for equality testing.
			 * @returns {boolean} <code>true</code> if left and right are within the provided epsilon, <code>false</code> otherwise.
			 */
			bool equalsEpsilon(Cartographic right, double epsilon = 0.0) {
				return Cartographic::equalsEpsilon(*this, right, epsilon);
			};

			/**
			 * Creates a string representing this cartographic in the format '(longitude, latitude, height)'.
			 *
			 * @returns {string} A string representing the provided cartographic in the format '(longitude, latitude, height)'.
			 */
			String toString() {
				return str_format("%d,%d,%d,%d", longitude, latitude, height);
			};
		};

	}
}

#endif
